class Rudoku::Sudoku
  attr_reader :fields
  attr_accessor :changed
  attr_accessor :rows, :columns, :squares, :lines, :houses, :floors, :towers, :chutes
  
  def initialize
    @fields = Array.new(9 * 9) { |i| Field.new(self, i % 9, i / 9) }
    init_units
    @changed = false
  end
  
  def self.from_str(str)
    return if str.length != 81
    s = new
    (0..80).each do |i|
      unless %w(_ x - 0 .).include? str[i].chr
        s[i] = str[i].chr.to_i 
        s[i].given = true
      end
    end
    s.changed = false
    return s
  end
    
  def [](*val)
    if val.size == 2 # 1, 2
      x = val[0] 
      y = val[1]
    elsif val.size == 1 # 19
      if val[0].kind_of? Integer
        x = val[0] % 9
        y = val[0] / 9
      else
        str = val[0].to_s # B3
        x = str[0] - ?A
        y = str[1] - ?1
      end
    else
      return nil
    end
    
    @fields[x + y * 9]
  end
  
  def []=(i, number) # also allows setting of multiple values: s[:B5] = [1, 2, 5]
    self[i].value = number
  end
  
  def solved?
    @fields.all? { |f| f.solved? } && consistent?
  end
  
  def changed?
    @changed
  end

  def consistent?      
    @houses.each do |house|
      found_values = []
      house.each do |f|
        f.value
        unless f.value.nil?
          return false if found_values.include?(f.value)
          found_values << f.value
        end
      end
    end
         
    return true
  end
  
  def solved_fields
    @fields.select { |f| f.solved? }
  end
  
  def unsolved_fields
    @fields.select { |f| !f.solved? }
  end  
  
  def init_units
    @rows = (0..8).collect { |i| @fields[(9 * i)..(9 * i + 8)] }
    @columns = (0..8).collect { |i| @fields.values_at(*(0..8).collect{ |j| 9 * j + i }) }
    @squares = (0..8).collect do |i|
      gx, gy = i % 3, i / 3
      (0..2).collect { |j| @fields[(27*gy + 3*gx + 9*j)..(27*gy + 3*gx +9*j + 2)] }.union
    end
    @lines = @rows + @columns
    @houses = @lines + @squares
    @floors = (0..2).collect { |i| (0..2).collect { |j| @rows[3*i + j] } }
    @towers = (0..2).collect { |i| (0..2).collect { |j| @columns[3*i + j] } }
    @chutes = @floors + @towers
  end
  
  def containing_square_pos(i, j)
    (i / 3) + 3 * (j / 3) # integerdeling => niet wegdoen!
  end
    
  def containing_square(i, j)
    @squares[containing_square_pos(i,j)]
  end
  
  def row_to_s(i)
    r = @rows[i]
    [r[0..2], r[3..5], r[6..8]].collect { |m| m.collect { |f| f.to_s }.join(" ") }.join("   ")
  end
  
  def to_s
    row_strings = (0..8).collect { |i| row_to_s(i) }
    [row_strings[0..2], row_strings[3..5], row_strings[6..8]].collect { |m| m.join("\n") }.join("\n\n")
  end
  
  def count_empty
    unsolved_fields.size
  end
  
  def count_guessed
    fields.select { |f| f.guessed? }.size
  end
  
  
  def copy_data(source)
    @changed = source.changed
    source.fields.each do |f|
      self[f.position].set_possibilities(f.possibilities)
      self[f.position].guessed = f.guessed
    end
  end
end
